﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using magic8ballUtility.Sintaksa;


namespace GeneratorSintaksnogAnalizatora
{
    public class Generator
    {
        List<SkupStavki> listaSkupovaStavki;
        public UlaznaSintaksa gramatika { get; set; }
        HashSet<string> znakoviGramatike;
        Dictionary<Produkcija, bool> posjecenostProdukcija;
        Dictionary<string, HashSet<string>> pocinjeZaZnakove;
        public Dictionary<int, Dictionary<string, string>> tablicaAkcija { get; set; }
        public Dictionary<int, Dictionary<string, string>> tablicaNovoStanje { get; set; }


        public Generator() { }

        public Generator(UlaznaSintaksa ulaz)
        {
            this.gramatika = ulaz;
            listaSkupovaStavki = new List<SkupStavki>();
            znakoviGramatike = new HashSet<string>();
            znakoviGramatike.UnionWith(ulaz.NezavrsniZnakovi);
            znakoviGramatike.UnionWith(ulaz.ZavrsniZnakovi);
            posjecenostProdukcija = new Dictionary<Produkcija, bool>();
            pocinjeZaZnakove = new Dictionary<string, HashSet<string>>();

        }

        public void Pokreni()
        {
            //Dodavanje novog pocetnog nezavrsnog znaka, npr. S' i produkcije S'->S
            string noviPocetniNezavrsni = gramatika.PocetniNezavrsniZnak + "\'";
            gramatika.NezavrsniZnakovi.Add(noviPocetniNezavrsni);
            gramatika.Produkcije.Insert(0, new Produkcija(noviPocetniNezavrsni, gramatika.PocetniNezavrsniZnak));

            //Inicijalizacija rjecnika koji biljezi je li neka produkcija vec posjecena u rekurzivnim pozivima
            foreach (var produkcija in gramatika.Produkcije)
            {
                posjecenostProdukcija.Add(produkcija, false);
            }

            IzracunajPocinjeZaZnakove();
            IzracunajStavke();
            PopuniTablice();
        }


        private void IzracunajStavke()
        {
            SkupStavki pocetniSkup = new SkupStavki();
            Stavka pocetnaStavka = new Stavka(0, 0, "§");
            pocetnaStavka.ReprezentSkupa = true;
            pocetniSkup.Add(pocetnaStavka);
            pocetniSkup = Okruzenje(pocetniSkup);
            listaSkupovaStavki.Add(pocetniSkup);
            int brojSkupova = 1;
            bool dodanNoviSkup = false;
            HashSet<SkupStavki> noviSkupovi = new HashSet<SkupStavki>();
            HashSet<SkupStavki> trenutniSkupovi = new HashSet<SkupStavki>();
            trenutniSkupovi.Add(pocetniSkup);

            do
            {
                dodanNoviSkup = false;
                foreach (var skupStavki in trenutniSkupovi)
                {
                    foreach (var znak in znakoviGramatike)
                    {
                        var noviSkup = Prijelaz(skupStavki, znak);
                        if (noviSkup.Count > 0 &&
                            !(listaSkupovaStavki.Contains(noviSkup)) &&
                            !(noviSkupovi.Contains(noviSkup)))
                        {
                            noviSkupovi.Add(noviSkup); 
                        }
                    }
                }

                foreach (var skupStavki in noviSkupovi)
                {
                    bool vecPostoji = false;

                    if (trenutniSkupovi.Contains(skupStavki))
                    {
                        vecPostoji = true;
                    }
                    
                    if (!vecPostoji)
                    {
                        listaSkupovaStavki.Add(skupStavki);
                    }

                }

                trenutniSkupovi.Clear();
                trenutniSkupovi.UnionWith(noviSkupovi);
                noviSkupovi.Clear();


                if (listaSkupovaStavki.Count > brojSkupova)
                {
                    dodanNoviSkup = true;
                    brojSkupova = listaSkupovaStavki.Count;
                }
            } while (dodanNoviSkup);

        }

        private void PopuniTablice()
        {
            tablicaAkcija = new Dictionary<int, Dictionary<string, string>>();
            tablicaNovoStanje = new Dictionary<int, Dictionary<string, string>>();

            #region Stvaranje svih redaka i stupaca u tablicama

            foreach (var skupStavki in listaSkupovaStavki)
            {
                int redniBrojStanja = listaSkupovaStavki.IndexOf(skupStavki);

                tablicaAkcija.Add(redniBrojStanja, new Dictionary<string, string>());
                foreach (var zavrsniZnak in gramatika.ZavrsniZnakovi)
                {
                    tablicaAkcija[redniBrojStanja].Add(zavrsniZnak, null);
                }

                tablicaAkcija[redniBrojStanja].Add("§", null);

                tablicaNovoStanje.Add(listaSkupovaStavki.IndexOf(skupStavki), new Dictionary<string, string>());
                foreach (var nezavrsniZnak in gramatika.NezavrsniZnakovi)
                {
                    tablicaNovoStanje[redniBrojStanja].Add(nezavrsniZnak, null);
                }
            }
            #endregion

            #region Popunjavanje tablice akcija

            //za svako stanje
            foreach (var stanje in tablicaAkcija.Keys)
            {
                //za svaku stavku u odgovarajucem skupu stavki
                foreach (var stavka in listaSkupovaStavki[stanje])
                {
                    Produkcija pripadajucaProdukcija = gramatika.Produkcije[stavka.RbrProdukcije];

                    //ako tocka nije na krajnje desnoj poziciji
                    if (stavka.PozicijaTocke != (pripadajucaProdukcija.Tijelo.Split(' ')).Length)
                    {
                        string znakIzaTocke = (pripadajucaProdukcija.Tijelo.Split(' '))[stavka.PozicijaTocke];

                        //ako je znak iza tocke zavrsni znak stavlja se shift
                        if (gramatika.ZavrsniZnakovi.Contains(znakIzaTocke))
                        {
                            //dodaj shift
                            var slijedeciSkup = Prijelaz(listaSkupovaStavki[stanje], znakIzaTocke);
                            var indeks = listaSkupovaStavki.IndexOf(slijedeciSkup);
                            tablicaAkcija[stanje][znakIzaTocke] = "s" + indeks;
                        }
                    }
                    //ako je tocka na kraju stavlja se reduce ili accept
                    else
                    {
                        //ako je polje prazno
                        if (tablicaAkcija[stanje][stavka.Lookahead] == null)
                        {
                            tablicaAkcija[stanje][stavka.Lookahead] = "r" + stavka.RbrProdukcije;
                        }
                        //ako polje nije prazno - konflikt
                        else
                        {
                            string poruka = null;

                            string sadrzajPolja = tablicaAkcija[stanje][stavka.Lookahead];

                            //"reduciraj/reduciraj" konflikt
                            if (sadrzajPolja.StartsWith("r"))
                            {
                                if (sadrzajPolja.CompareTo("r" + stavka.RbrProdukcije) < 0)
                                {
                                    poruka = "reduciraj/reduciraj konflikt, ostavljena postojeca redukcija";
                                }
                                else
                                {
                                    tablicaAkcija[stanje][stavka.Lookahead] = "r" + stavka.RbrProdukcije;
                                    poruka = "reduciraj/reduciraj konflikt, upisana nova redukcija";
                                }
                            }
                            //"pomakni/reduciraj" konflikt
                            else
                            {
                                poruka = "pomakni/reduciraj konflikt, ostavljena akcija pomakni";
                            }

                            PrijaviKonflikt(stanje, stavka.Lookahead, poruka);

                        }

                        //ako je stavka oblika S'->S. , § - upisuje se "accept"
                        if (stavka.RbrProdukcije == 0 && stavka.PozicijaTocke == 1 && stavka.Lookahead == "§")
                        {
                            tablicaAkcija[stanje][stavka.Lookahead] = "accept";
                        }
                    }
                }
            }
            #endregion

            #region Popunjavanje tablice novih stanja

            foreach (var stanje in tablicaNovoStanje.Keys)
            {
                foreach (var nezavrsniZnak in gramatika.NezavrsniZnakovi)
                {

                    var noviSkup = Prijelaz(listaSkupovaStavki[stanje], nezavrsniZnak);
                    if (noviSkup.Count != 0)
                    {
                        int indeksStanja = listaSkupovaStavki.IndexOf(noviSkup);
                        tablicaNovoStanje[stanje][nezavrsniZnak] = indeksStanja.ToString();
                    }
                }
            }

            #endregion

        }

        private void PrijaviKonflikt(int stanje, string znak, string poruka)
        {
            Console.WriteLine("Konflikt u stanju {0}, za znak {1} - {2}", stanje, znak, poruka);
        }

        private SkupStavki Okruzenje(SkupStavki polazniSkup)
        {
            //slucaj kada se metoda praziva za prazan skup
            if (polazniSkup.Count == 0)
            {
                return new SkupStavki();
            }

            //slucaj kada je tocka na kraju polazne stavke
            {
                Stavka polaznaStavka = new Stavka();
                foreach (var stavka in polazniSkup)
                {
                    if (stavka.ReprezentSkupa)
                    {
                        polaznaStavka = stavka;
                    }
                }

                Produkcija polaznaProdukcija = gramatika.Produkcije[polaznaStavka.RbrProdukcije];

                if (polaznaStavka.PozicijaTocke == polaznaProdukcija.Tijelo.Split(' ').Length)
                {
                    return polazniSkup;
                }
            }

            //kada tocka nije na kraju polazne stavke
            SkupStavki privremeniSkup = new SkupStavki();
            privremeniSkup.UnionWith(polazniSkup);
            SkupStavki skupNovihStavki = new SkupStavki();

            bool dodaneNoveStavke = false;
            do
            {
                dodaneNoveStavke = false;

                foreach (var stavka in polazniSkup)
                {
                    //ako stavka ima tocku na kraju treba ju preskociti
                    if (stavka.PozicijaTocke == (gramatika.Produkcije[stavka.RbrProdukcije].Tijelo.Split(' ')).Length)
                    {
                        continue;
                    }
                    Produkcija polaznaProdukcija = gramatika.Produkcije[stavka.RbrProdukcije];
                    string znakIzaTocke = (polaznaProdukcija.Tijelo.Split(' '))[stavka.PozicijaTocke];
                    string[] znakoviTijelaPolazneProdukcije = polaznaProdukcija.Tijelo.Split(' ');
                    StringBuilder privremeniNiz = new StringBuilder();


                    //Pronalazenje niza desno od znaka iza tocke (potrebno ubacivati razmake)
                    for (int i = stavka.PozicijaTocke + 1; i < znakoviTijelaPolazneProdukcije.Length; i++)
                    {
                        privremeniNiz.Append(znakoviTijelaPolazneProdukcije[i]);
                        if (i != znakoviTijelaPolazneProdukcije.Length - 1)
                        {
                            privremeniNiz.Append(' ');
                        }
                    }

                    string nizDesnoOdZnakaIzaTocke = privremeniNiz.ToString();

                    foreach (var produkcija in gramatika.Produkcije)
                    {
                        if (produkcija.Glava == znakIzaTocke)
                        {
                            int redniBroj = gramatika.Produkcije.IndexOf(produkcija);
                            foreach (var zavrsniZnak in PocinjeZaNiz((nizDesnoOdZnakaIzaTocke + ' ' + stavka.Lookahead).TrimStart(' ')))
                            {
                                if (!(privremeniSkup.Contains(new Stavka(redniBroj, 0, zavrsniZnak))))
                                {
                                    privremeniSkup.Add(new Stavka(redniBroj, 0, zavrsniZnak));
                                    skupNovihStavki.Add(new Stavka(redniBroj, 0, zavrsniZnak));
                                    dodaneNoveStavke = true;
                                }

                            }
                        }
                    }
                }

                polazniSkup.Clear();

                foreach (var stavka in skupNovihStavki)
                {
                    polazniSkup.Add(stavka);
                }

                skupNovihStavki.Clear();

            } while (dodaneNoveStavke);


            return privremeniSkup;
        }

        private SkupStavki Prijelaz(SkupStavki polazniSkup, string prijelazniZnak)
        {
            //#region Slucaj kada se poziva za "krajnji" skup

            //Stavka prvaStavka = new Stavka();
            //foreach (var stavka in polazniSkup)
            //{
            //    if (stavka.ReprezentSkupa)
            //    {
            //        prvaStavka = stavka;
            //    }
            //}

            //Produkcija polaznaProdukcija = gramatika.Produkcije[prvaStavka.RbrProdukcije];
            //if (prvaStavka.PozicijaTocke == polaznaProdukcija.Tijelo.Split(' ').Length)
            //{
            //    return new SkupStavki();
            //}

            //#endregion


            //SkupStavki privremeniSkup = new SkupStavki();
            //bool ubacenaPrvaStavka = false;
            //foreach (var stavka in polazniSkup)
            //{
            //    //ako stavka ima tocku na kraju treba ju preskociti
            //    if (stavka.PozicijaTocke == (gramatika.Produkcije[stavka.RbrProdukcije].Tijelo.Split(' ')).Length)
            //    {
            //        continue;
            //    }

            //    Produkcija produkcija = gramatika.Produkcije[stavka.RbrProdukcije];

            //    if ((produkcija.Tijelo.Split(' '))[stavka.PozicijaTocke] == prijelazniZnak)
            //    {
            //        int redniBroj = gramatika.Produkcije.IndexOf(produkcija);
            //        var novaStavka = new Stavka(redniBroj, stavka.PozicijaTocke + 1, stavka.Lookahead);

            //        if (!ubacenaPrvaStavka)
            //        {
            //            novaStavka.ReprezentSkupa = true;
            //            ubacenaPrvaStavka = true;
            //        }
            //        privremeniSkup.Add(novaStavka);

            //    }
            //}
            SkupStavki privremeniSkup = new SkupStavki();

            foreach (var stavka in polazniSkup)
            {
                if (stavka.PozicijaTocke == gramatika.Produkcije[stavka.RbrProdukcije].Tijelo.Split(' ').Length)
                {
                    if (stavka.ReprezentSkupa)
                        return privremeniSkup;
                    continue;
                }
                else
                {
                    var znak = gramatika.Produkcije[stavka.RbrProdukcije].Tijelo.Split(' ')[stavka.PozicijaTocke];
                    if (znak.Equals(prijelazniZnak))
                    {
                        privremeniSkup.Add(new Stavka(stavka.RbrProdukcije, stavka.PozicijaTocke+1, stavka.Lookahead));
                    }
                }
            }
            return Okruzenje(privremeniSkup);

        }

        private HashSet<string> PocinjeZaNiz(string nizZnakova)
        {
            HashSet<string> skupPocinje = new HashSet<string>();

            string[] znakovi = nizZnakova.Split(' ');
            for (int i = 0; i < znakovi.Length; i++)
            {
                var znakoviZaDodati = pocinjeZaZnakove[znakovi[i]];

                bool dodajSlijedeciZnak = false;

                foreach (var znakZaDodati in znakoviZaDodati)
                {
                    if (znakZaDodati != "$")
                    {
                        skupPocinje.Add(znakZaDodati);
                    }
                    else
                    {
                        dodajSlijedeciZnak = true;
                    }
                }

                if ((i == znakovi.Length - 1) && znakoviZaDodati.Contains("$"))
                {
                    skupPocinje.Add("$");
                }

                if (!dodajSlijedeciZnak)
                {
                    break;
                }

            }

            return skupPocinje;
        }

        private void IzracunajPocinjeZaZnakove()
        {
            foreach (var znak in znakoviGramatike)
            {
                pocinjeZaZnakove[znak] = PocinjeZaZnak(znak);
                OcistiPosjecenost();
            }
            pocinjeZaZnakove["§"] = PocinjeZaZnak("§");
        }

        private HashSet<string> PocinjeZaZnak(string znak)
        {
            //ako je znak zavrsni znak, onda ga dodajemo
            if (gramatika.ZavrsniZnakovi.Contains(znak))
            {
                var skupPocinje = new HashSet<string>();
                skupPocinje.Add(znak);
                return skupPocinje;
            }
            else if (znak == "§")
            {
                var skupPocinje = new HashSet<string>();
                skupPocinje.Add(znak);
                return skupPocinje;
            }
            //ako je nezavrsni potrebno je obraditi njegove produkcije
            else
            {
                var skupPocinje = new HashSet<string>();

                foreach (var produkcija in gramatika.Produkcije)
                {
                    //odabiranje odgovarajucih produkcija
                    if (produkcija.Glava == znak)
                    {
                        if (posjecenostProdukcija[produkcija] == false)
                        {

                            //ne-epsilon produkcije
                            if (!(produkcija.Tijelo == "$"))
                            {
                                string[] desniZnakovi = produkcija.Tijelo.Split(' ');
                                for (int i = 0; i < desniZnakovi.Length; i++)
                                {
                                    posjecenostProdukcija[produkcija] = true;

                                    var znakoviZaDodati = PocinjeZaZnak(desniZnakovi[i]);

                                    //dodavanje ne-epsilon znakova
                                    foreach (var znakZaDodati in znakoviZaDodati)
                                    {
                                        if (znakZaDodati != "$")
                                        {
                                            skupPocinje.Add(znakZaDodati);
                                        }
                                    }

                                    //ako desni znak ne generira prazni niz, treba prekinuti dodavanje znakova
                                    if (!(znakoviZaDodati.Contains("$")))
                                    {
                                        break;
                                    }
                                    //ako smo dosli do desnog znaka, znaci da svi generiraju prazni niz i da treba dodati epsilon u skup
                                    if (i == desniZnakovi.Length - 1)
                                    {
                                        skupPocinje.Add("$");
                                    }
                                }
                            }
                            //epsilon produkcije
                            else
                            {
                                skupPocinje.Add("$");
                            }
                        }
                    }
                }


                return skupPocinje;
            }
        }

        private void OcistiPosjecenost()
        {
            foreach (var produkcija in gramatika.Produkcije)
            {
                posjecenostProdukcija[produkcija] = false;
            }
        }

    }
}
